# Copyright (c) 2018 Presto Labs Pte. Ltd.
# Author: leon

import ujson as json
import functools

from coin.base.book.types import TradeSide, create_market_trade_event
from coin.exchange.base.ws.handler import WebsocketHandler
from coin.exchange.quoinex_v2.book_builder import BookBuilder
from coin.exchange.quoinex_v2.kr_rest.product import QuoinexProduct


@functools.lru_cache(maxsize=32)
def get_product_from_channel(channel):
  if channel.startswith('price_ladders_cash'):
    # Depth case.
    exploded = channel.split('_')
    assert len(exploded) in [4, 5]
    symbol = exploded[3]
    return QuoinexProduct.FromStrNativeProduct(symbol.upper())
  elif channel.startswith('executions_cash'):
    # Trade case.
    exploded = channel.split('_')
    assert len(exploded) == 3
    symbol = exploded[-1]
    return QuoinexProduct.FromStrNativeProduct(symbol.upper())
  else:
    raise ValueError(type)


@functools.lru_cache(maxsize=32)
def get_side_from_channel(channel):
  assert channel.startswith('price_ladders_cash')
  exploded = channel.split('_')
  assert len(exploded) == 5
  side = exploded[-1]
  return side


class QuoinexHandler(WebsocketHandler):
  def on_ws_depth(self, record, queue_data, topic_data):
    data = record.data
    if not isinstance(data, dict):
      data = json.loads(data.tobytes())

    assert 'channel' in data
    channel = data['channel']
    product = get_product_from_channel(channel)
    native_symbol = product.native_symbol
    if not self.is_subscribed('book', native_symbol):
      return

    self._books.setdefault(native_symbol, BookBuilder(native_symbol))
    book = self._books[native_symbol]
    side = get_side_from_channel(channel)
    bids = None
    asks = None
    if side == 'buy':
      bids = json.loads(data['data'])
    elif side == 'sell':
      asks = json.loads(data['data'])
    else:
      raise ValueError(data)
    book.snapshot(bids, asks)
    if book.is_cross_book():
      # Due to bid ask not coming together, cross book could happen.
      return
    book.update_timestamp(record.timestamp)
    self.ready = True
    if self._feed_checker:
      self._feed_checker.on_book(product=product, book=book, timestamp=record.timestamp)
    self.publish_event('book', book.symbol, book)

  def on_ws_depth_single_channel(self, record, queue_data, topic_data):
    data = record.data
    if not isinstance(data, dict):
      data = json.loads(data.tobytes())

    assert 'channel' in data
    channel = data['channel']
    product = get_product_from_channel(channel)
    native_symbol = product.native_symbol
    if not self.is_subscribed('book', native_symbol):
      return

    book = BookBuilder(native_symbol, book_builder_observer=self._book_builder_observer)
    bids_asks = json.loads(data['data'])
    bids = bids_asks['bids']
    asks = bids_asks['asks']
    book.snapshot(bids, asks)
    book.update_timestamp(record.timestamp)
    book.update_topic_id(record.topic_id)
    self._books[native_symbol] = book
    self.ready = True
    if self._feed_checker:
      self._feed_checker.on_book(product=product, book=book, timestamp=record.timestamp)
    self.publish_event('book', book.symbol, book)
    book.reset_observer()

  def on_ws_trade(self, record, queue_data, topic_data):
    data = record.data
    if not isinstance(data, dict):
      data = json.loads(data.tobytes())

    assert 'channel' in data
    channel = data['channel']
    product = get_product_from_channel(channel)
    native_symbol = product.native_symbol
    if not self.is_subscribed('trade', native_symbol):
      return

    data = json.loads(data['data'])
    price = float(data['price'])
    qty = float(data['quantity'])
    if data['taker_side'] == 'buy':
      side = TradeSide.TRADE_BUY_SIDE
    elif data['taker_side'] == 'sell':
      side = TradeSide.TRADE_SELL_SIDE
    else:
      raise ValueError('Unknown side: %s', data)
    trade_event = create_market_trade_event(record.timestamp,
                                            native_symbol,
                                            price,
                                            qty,
                                            side,
                                            topic_id=record.topic_id)
    if self._feed_checker:
      self._feed_checker.on_trade(product=product,
                                  trade_event=trade_event,
                                  timestamp=record.timestamp)
    self.publish_event('trade', native_symbol, trade_event)
